
local function get_lcm(a,b)
    local mult = a * b;
    local gcd = 1;
    while true do
        if(a==0)then
            gcd = b;
            break;
        end
        b = b % a;
        if(b==0)then
            gcd = a;
            break;
        end
        a = a % b;
    end
    return math.floor(mult/gcd);
end

PFD_MAX = 4
VCO_MIN = 600
VCO_MAX = 1250
VCO_LOW = VCO_MIN >> 1
local ii = table.insert;

DesignInfo = {};
DesignInfo.PLLCLK_FREQ =  {60,100};
DesignInfo.PLLCLK_PHASE = {0,0,0,0,0};
local function int(v)
    return math.floor(v);
end
local abs = math.abs;

function get_pll_phase_config(clkdiv, phase)
  shift = phase * clkdiv / 360
  clkout_del = int(shift)
  clkout_phase = int((shift - clkout_del) * 8 + 0.5)
  return clkout_del, clkout_phase
end

function get_pll_phase_val(freq, phase)
    if(freq and freq~=0)then return int(1e6 / freq * phase / 360 + 0.5); end
    return 0;
end

function get_pll_phase_str(clkout_div, clkout_del, clkout_phase)
    if(clkout_div==0 or not clkout_div)then
        phase = 0;
        return '';
    else
        phase = (clkout_del + clkout_phase / 8) * 360 / clkout_div
    end
  --return f", phase: {phase} deg" if phase else ""
   return string.format(', phase: %.3f deg',phase);
end


local function get_pll_phase_err(vco)
  phase_err = 0
  for i=1,5 do
    if DesignInfo.PLLCLK_FREQ[i] and DesignInfo.PLLCLK_PHASE[i]~=0 then
      clk_div = int(vco / DesignInfo.PLLCLK_FREQ[i])
      clk_del, clk_phase = get_pll_phase_config(clk_div, DesignInfo.PLLCLK_PHASE[i])
      phase_err = phase_err + abs((clk_del + clk_phase / 8) / clk_div * 360 - DesignInfo.PLLCLK_PHASE[i])
    end
  end
  return phase_err
end

local function get_pll_vco(freq_array)
    local lcm = 1;
    for k,v in ipairs(freq_array)do
        local freq = v;
        if(freq~=0)then
            --print(lcm);
            lcm = get_lcm(lcm,int(freq*1000+0.5));
        end
    end
    lcm = lcm / 1000.0;
    local phase_err = 1e9;
    local vco = nil;
    --print(int((VCO_LOW-1)/lcm+1),int(VCO_MAX/lcm+1));
    for k=int((VCO_LOW-1)/lcm+1),int(VCO_MAX/lcm+1) do
        local m = k;
        local err = get_pll_phase_err(m * lcm);
        --print(err,phase_err);
        if err < phase_err then
            phase_err = err
            vco = m * lcm
        end
    end
    --print(vco);
    assert(vco);
    assert(vco~=0);
    return vco;
end

local function get_pll_div(div)
    local divh = (div>>1) - 1;
    if(div<=1)then divh = 255; end
    local divl = (div-divh)-2;
    if(div<=1)then divl = 255; end
    local trim = 1;
    if(divh==divl)then trim = 0; end
    local byp = 0;
    if(div==1)then byp = 1; end
    return divh,divl,trim,byp;
end

local hseclk_freq = 12;
local clkin_div = int((hseclk_freq-1)/4)+1;
print(hseclk_freq/clkin_div);
local a = {};
ii(a,hseclk_freq);
for k,v in ipairs(DesignInfo.PLLCLK_FREQ)do ii(a,v); end
local vco = get_pll_vco({hseclk_freq/clkin_div,60,100});
local clkfb_div = int(vco*clkin_div/hseclk_freq+0.5);
local clkout_div = {};
local clkout_phase = {};
local clkout_del = {};
for i=1,5 do
    if(DesignInfo.PLLCLK_FREQ[i] and DesignInfo.PLLCLK_FREQ[i]~=0)then
        --print(DesignInfo.PLLCLK_FREQ[i]);
        clkout_div[i] = int(vco/DesignInfo.PLLCLK_FREQ[i] + 0.5);
    end
    clkout_del[i],clkout_phase[i] = get_pll_phase_config(clkout_div[i] or 0,DesignInfo.PLLCLK_PHASE[i]);
end
print(vco,clkin_div,clkfb_div);

print(string.format('HSE clock:%.2f',hseclk_freq));
print(string.format('VCO clock:%.2f',vco));
print(string.format('PLL clock:%.2f',DesignInfo.PLLCLK_FREQ[2]));


print(string.format('divh:%d,  divl:%d,  trim:%d,  byp:%d',get_pll_div(clkin_div)));
print(string.format('divh:%d,  divl:%d,  trim:%d,  byp:%d',get_pll_div(clkfb_div)));
print(string.format('divh:%d,  divl:%d,  trim:%d,  byp:%d',get_pll_div(clkout_div[1])));